package org.interaction3d.assembly.sax;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Locale;
import java.util.Scanner;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.interaction3d.assembly.Material;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import static javax.xml.XMLConstants.*;


/**
 * @author Michael Nischt
 * @version 0.1
 */
public class SAXMaterialReader
{
    // <editor-fold defaultstate="collapsed" desc=" MaterialHandler ">

    private static void parseDoubleArray(CharSequence floats, double[] values) {

        final Scanner scanner = new Scanner(floats.toString()).useLocale(Locale.ENGLISH);
        
        for( int numMatches=0; numMatches<values.length; numMatches++)
        {
            values[numMatches] = scanner.nextDouble();
        }
    }

    private class MaterialHandler extends DefaultHandler
    {
        private final Material material;

        MaterialHandler(Material material)
        {
            if(material == null)
            {
                throw new NullPointerException();
            }
            this.material = material;
        }

        private String name;
        private int dimension;

        private Locator locator;
        private AttributeParser ap;
        private boolean readCData = false;
        private StringBuilder cdata = new StringBuilder();

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException
        {
            if(readCData)
            {
                this.cdata.append(ch, start, length);
            }
        }

        @Override
        public void setDocumentLocator(Locator locator)
        {
            this.ap = new AttributeParser(locator);
            this.locator = locator;
        }


        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            //System.out.println(qName);
            if(qName.equals("Material"))
            {
            }
            else if(qName.equals("Constants"))
            {
                int numConstants = this.ap.parseInt(attributes, "count");
                material.constants(numConstants);

            }
            else if(qName.equals("Constant"))
            {
                this.name = this.ap.parseString(attributes, "name");
                this.dimension = this.ap.parseInt(attributes, "dimension");

                if(dimension < 1 || dimension > 4 )
                {
                    throw new SAXException("Unsupported dimension: 1 <= " + dimension + " <= 4");
                }

            }
            else if(qName.equals("Textures"))
            {
                int numTextures = this.ap.parseInt(attributes, "count");
                material.textures(numTextures);
            }
            else if(qName.equals("Texture"))
            {
                this.name = this.ap.parseString(attributes, "name");
            }

            cdata.setLength(0);
            this.readCData = true;

        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException  {
            this.readCData = false;
            
            if(qName.equals("Shader"))
            {
                String shader = cdata.toString().trim();
                material.shader(shader);
            }
            else if(qName.equals("Constant"))
            {
                double[] constant = new double[dimension];
                parseDoubleArray(this.cdata, constant);

                material.constant(name, constant);

            }

            else if(qName.equals("Texture"))
            {
                String source = cdata.toString().trim();
                material.texture(name, source);
            }            
        }


        @Override
        public void error(SAXParseException e)
        {
            String msg = "Error at " + locator.getLineNumber()  + ":" + locator.getColumnNumber();
            handle(e, msg);
        }

        @Override
        public void fatalError(SAXParseException e)
        {
            String msg = "Fatal error at " + locator.getLineNumber()  + ":" + locator.getColumnNumber();
            handle(e, msg);
        }

        @Override
        public void warning(SAXParseException e)
        {
            String msg = "Warning at " + locator.getLineNumber()  + ":" + locator.getColumnNumber();
            handle(e, msg);
        }

    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc=" Variables ">

    private final SAXParser parser;

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc=" Constructors ">

    private SAXMaterialReader(SAXParserFactory factory) throws ParserConfigurationException, SAXException
    {
        URL schemaURL = getClass().getResource("/org/interaction3d/assembly/Material.xsd");
        Schema schema = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI).newSchema(schemaURL);
        factory.setSchema(schema);
        factory.setNamespaceAware(true);//impoartant for xsd
        factory.setValidating(false);//only for dtd
        parser = factory.newSAXParser();
    }

    public SAXMaterialReader() throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException
    {
        this(SAXParserFactory.newInstance());
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc=" Methods ">

    public Material.Assembly assembly(final String uri)
    {
        return new Material.Assembly()
        {
            public void assemble(Material mesh)
            {
                try
                {
                    parser.parse(uri, new MaterialHandler(mesh));
                }
                catch (SAXException ex)
                {
                    handle(ex);
                }
                catch (IOException ex)
                {
                    handle(ex);
                }
            }
        };
    }

    public Material.Assembly assembly(final InputSource is)
    {
        return new Material.Assembly()
        {
            public void assemble(Material mesh)
            {
                try
                {
                    parser.parse(is, new MaterialHandler(mesh));
                }
                catch (SAXException ex)
                {
                    handle(ex);
                }
                catch (IOException ex)
                {
                    handle(ex);
                }
            }
        };
    }

    public Material.Assembly assembly(final InputStream in)
    {
        return new Material.Assembly()
        {
            public void assemble(Material material)
            {
                try
                {
                    parser.parse(in, new MaterialHandler(material));
                }
                catch (SAXException ex)
                {
                    handle(ex);
                }
                catch (IOException ex)
                {
                    handle(ex);
                }
            }
        };
    }

    public Material.Assembly assembly(final File f)
    {
        return new Material.Assembly()
        {
            public void assemble(Material material)
            {
                try
                {
                    parser.parse(f, new MaterialHandler(material));
                }
                catch (SAXException ex)
                {
                    handle(ex);
                }
                catch (IOException ex)
                {
                    handle(ex);
                }
            }
        };
    }
    
    // </editor-fold>


    static private void handle(Exception ex)
    {
        handle(ex, null);
    }

    static private void handle(Exception ex, String msg)
    {
        Logger.getLogger(SAXMeshReader.class.getName()).log(Level.SEVERE, msg, ex);
        throw new RuntimeException(ex);
    }

    static
    {
        Logger logger = Logger.getLogger(SAXMaterialReader.class.getName());
        logger.setLevel(Level.ALL);
        logger.addHandler(new ConsoleHandler());
    }
}